package com.baize.filedowndemo.utils

import android.annotation.SuppressLint
import android.content.*
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.*
import androidx.core.content.FileProvider
import com.baize.filedowndemo.App
import java.io.*
import java.util.*
import android.content.ContentResolver

import android.content.res.AssetFileDescriptor
import android.util.Log
import android.widget.Toast
import okio.BufferedSink
import okio.Okio


/**
 * # FileUri
 *
 * - Uri & Path Tool
 *
 * @author javakam
 * @date 2020/8/24  11:24
 */
object FileUri {

  const val AUTHORITY = ".baizeFileProvider"

  fun writeFileToLocal(file: File) { //高版本适配：>= 29
    if (!file.exists()) {
      Log.e("baize_", "下载的文件不存在：${file.absolutePath}")
      return
    }
    val targetFile = File(
      Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
      "/" + file.name
    )
    if (targetFile.exists()) {
      Log.e("baize_", "下载的文件已经存在：${file.absolutePath}")
      return
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      try {
        val inputStream = FileInputStream(file)
        val bis = BufferedInputStream(inputStream)
        val startWrite = System.currentTimeMillis()
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
//        values.put(MediaStore.MediaColumns.TITLE, file.name)
//        values.put(MediaStore.MediaColumns.MIME_TYPE, FileMimeType.getMimeType(file.absolutePath))
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)

//        val downloadUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
//        Log.i("baize_", "Download Uri:${downloadUri} inUri:${MediaStore.Downloads.INTERNAL_CONTENT_URI}")

//        App.getApplication().contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI,null,null)
        App.getApplication().contentResolver.refresh(MediaStore.Downloads.EXTERNAL_CONTENT_URI,null,null)
        val uri = App.getApplication().contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
          val outputStream = App.getApplication().contentResolver.openOutputStream(uri)
          if (outputStream != null) {
            val bos = BufferedOutputStream(outputStream)
            val buffer = ByteArray(1024)
            var bytes = bis.read(buffer)
            while (bytes >= 0) {
              bos.write(buffer, 0, bytes)
              bos.flush()
              bytes = bis.read(buffer)
            }
            bos.close()
          }
        }
        Log.i("baize_", "拷贝耗时: ${System.currentTimeMillis() - startWrite} uri:${uri}")
      } catch (e: Exception) {
        Log.e("baize_", "复制到下载目录错误：${e.message}")
      }
    } else {
      file.copyTo(targetFile, true)
      Log.e("baize_", "writeFileToLocal: 版本过低, 复制到:${targetFile.absolutePath}")
    }
//    if (file.exists()) {
//      file.delete()
//    }
  }

  //    fun isAndroidQFileExists(context: Context?, path: String?): Boolean {
  //        if (context == null) {
  //            return false
  //        }
  //        var afd: AssetFileDescriptor? = null
  //        val cr = context.contentResolver
  //        try {
  //            val uri = Uri.parse(path)
  //            afd = cr.openAssetFileDescriptor(Uri.parse(path), "r")
  //            afd?.let { close(it) } ?: return false
  //        } catch (e: FileNotFoundException) {
  //            return false
  //        } finally {
  //            close(afd)
  //        }
  //        return true
  //    }


  /**
   * 1. 检查 Uri 是否正确
   * 2. Uri 对应的文件是否存在 (可能是已删除, 也肯是系统 db 存有 Uri 相关记录, 但是文件失效或者损坏)
   *
   * EN
   * 1. Check if Uri is correct
   * 2. Whether the file corresponding to Uri exists (may be deleted, or the system db has Uri related records, but the file is invalid or damaged)
   *
   * https://stackoverflow.com/questions/7645951/how-to-check-if-resource-pointed-by-uri-is-available
   */
  fun checkUri(uri: Uri?): Boolean {
    if (uri == null) return false
    val resolver = App.getApplication().contentResolver

    //1. Check Uri
    var cursor: Cursor? = null
    val isUriExist: Boolean = try {
      cursor = resolver.query(
        uri,
        null,
        null,
        null,
        null
      ) //cursor null: content Uri was invalid or some other error occurred
      //cursor.moveToFirst() false: Uri was ok but no entry found.
      (cursor != null && cursor.moveToFirst())
    } catch (t: Throwable) {
      FileLogger.e("1.Check Uri Error: ${t.message}")
      false
    } finally {
      try {
        cursor?.close()
      } catch (t: Throwable) {
      }
    }

    //2. Check File Exist
    //如果系统 db 存有 Uri 相关记录, 但是文件失效或者损坏 (If the system db has Uri related records, but the file is invalid or damaged)
    var ins: InputStream? = null
    val isFileExist: Boolean = try {
      ins = resolver.openInputStream(uri) // file exists
      true
    } catch (t: Throwable) { // File was not found eg: open failed: ENOENT (No such file or directory)
      FileLogger.e("2. Check File Exist Error: ${t.message}")
      false
    } finally {
      try {
        ins?.close()
      } catch (t: Throwable) {
      }
    }
    return isUriExist && isFileExist
  }

  //Android R
  //----------------------------------------------------------------

  /**
   * `MANAGE_EXTERNAL_STORAGE` 权限检查
   *
   * @return `true` Have permission
   */
  fun isExternalStorageManager(): Boolean =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Environment.isExternalStorageManager() else false

  /**
   * 跳转到 `MANAGE_EXTERNAL_STORAGE` 权限设置页面
   *
   * @return `true` Has been set
   */
  fun jumpManageAppAllFilesPermissionSetting(
    context: Context,
    isNewTask: Boolean = false,
  ): Boolean {
    if (isExternalStorageManager()) return true

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      try {
        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
        intent.data = Uri.parse("package:${context.packageName}")
        if (isNewTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        context.startActivity(intent)
      } catch (e: Exception) {
        FileLogger.e("jumpManageAppAllFilesPermissionSetting: $e")
      }
    }
    return false
  }

  //从 FilePath 中获取 Uri (Get Uri from FilePath)
  //----------------------------------------------------------------

  fun getUriByPath(path: String?): Uri? =
    if (path.isNullOrBlank()) null else getUriByFile(File(path))

  /**
   * Return a content URI for a given file.
   *
   * @param file The file.
   * @param isOriginal true content://  or file:// ; false file://xxx
   * @return a content URI for a given file
   */
  fun getUriByFile(file: File?, isOriginal: Boolean = false): Uri? {
    return if (isOriginal) Uri.fromFile(file)
    else {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        val authority = App.getApplication().packageName + AUTHORITY
        FileProvider.getUriForFile(App.getApplication(), authority, file ?: return null)
      } else {
        Uri.fromFile(file)
      }
    }
  }

  fun getShareUri(path: String?): Uri? =
    if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = false)

  /**
   * @return content://  or  file://
   */
  fun getShareUri(file: File?): Uri? = getUriByFile(file, isOriginal = false)

  fun getOriginalUri(path: String?): Uri? =
    if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = true)

  /**
   * @return file://xxx
   */
  fun getOriginalUri(file: File?): Uri? = getUriByFile(file, isOriginal = true)

  //获取Uri对应的文件路径, Compatible with API 26
  //----------------------------------------------------------------

  /**
   * ### Get the file path through Uri
   *
   * - Need permission: RequiresPermission(permission.READ_EXTERNAL_STORAGE)
   *
   * - Modified from: https://github.com/coltoscosmin/FileUtils/blob/master/FileUtils.java
   *
   * @return file path
   */
  fun getPathByUri(uri: Uri?): String? {
    return uri?.use {
      FileLogger.i(
        "FileUri getPathByUri -> " + "Uri: " + uri + ", Authority: " + uri.authority + ", Fragment: " + uri.fragment + ", Port: " + uri.port + ", Query: " + uri.query + ", Scheme: " + uri.scheme + ", Host: " + uri.host + ", Segments: " + uri.pathSegments.toString()
      )

      // 以 file:// 开头的使用第三方应用打开 (open with third-party applications starting with file://)
      if (ContentResolver.SCHEME_FILE.equals(uri.scheme, ignoreCase = true))
        return getDataColumn(uri)

      @SuppressLint("ObsoleteSdkInt") val isKitKat =
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT

      // Before 4.4 , API 19 content:// 开头, 比如 content://media/external/images/media/123
      if (!isKitKat && ContentResolver.SCHEME_CONTENT.equals(uri.scheme, true)) {
        if (isGooglePhotosUri(uri)) return uri.lastPathSegment
        return getDataColumn(uri)
      }

      val context = App.getApplication() // After 4.4 , API 19
      // DocumentProvider
      if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // LocalStorageProvider
        if (isLocalStorageDocument(uri)) { // The path is the id
          return DocumentsContract.getDocumentId(uri);
        } // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
          val docId = DocumentsContract.getDocumentId(uri)
          val split = docId.split(":").toTypedArray()
          val type = split[0]
          if ("primary".equals(type, ignoreCase = true)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
              return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
                .toString() + File.separator + split[1]
            } else {
              @Suppress("DEPRECATION") return Environment.getExternalStorageDirectory()
                .toString() + File.separator + split[1]
            }
          } else if ("home".equals(type, ignoreCase = true)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
              return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
                .toString() + File.separator + "documents" + File.separator + split[1]
            } else {
              @Suppress("DEPRECATION") return Environment.getExternalStorageDirectory()
                .toString() + File.separator + "documents" + File.separator + split[1]
            }
          } else {
            @Suppress("DEPRECATION") val sdcardPath = Environment.getExternalStorageDirectory()
              .toString() + File.separator + "documents" + File.separator + split[1]
            return if (sdcardPath.startsWith("file://")) {
              sdcardPath.replace("file://", "")
            } else {
              sdcardPath
            }
          }
        } // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
          val id = DocumentsContract.getDocumentId(uri)
          if (id != null && id.startsWith("raw:")) {
            return id.substring(4)
          }
          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            val contentUriPrefixesToTry = arrayOf(
              "content://downloads/public_downloads",
              "content://downloads/my_downloads",
              "content://downloads/all_downloads"
            )
            for (contentUriPrefix in contentUriPrefixesToTry) {
              val contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), id.toLong())
              try {
                val path = getDataColumn(contentUri)
                if (!path.isNullOrBlank()) return path
              } catch (e: Exception) {
                FileLogger.e(e.toString())
              }
            }
          } else { //testPath(uri)
            return getDataColumn(uri)
          }
        } // MediaProvider
        else if (isMediaDocument(uri)) {
          val docId = DocumentsContract.getDocumentId(uri)
          val split = docId.split(":").toTypedArray()
          val contentUri: Uri? = when (split[0]) {
            "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            "download" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
              MediaStore.Downloads.EXTERNAL_CONTENT_URI
            } else null
            else -> null
          }
          val selectionArgs = arrayOf(split[1])
          return getDataColumn(contentUri, "_id=?", selectionArgs)
        }

        //GoogleDriveProvider
        else if (isGoogleDriveUri(uri)) {
          return getGoogleDriveFilePath(uri, context)
        }
      } // MediaStore (and general)
      else if ("content".equals(uri.scheme, ignoreCase = true)) { // Return the remote address
        if (isGooglePhotosUri(uri)) {
          return uri.lastPathSegment
        } // Google drive legacy provider
        else if (isGoogleDriveUri(uri)) {
          return getGoogleDriveFilePath(uri, context)
        } // Huawei
        else if (isHuaWeiUri(uri)) {
          val uriPath = getDataColumn(uri)
            ?: uri.toString() //content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/
          if (uriPath.startsWith("/root")) {
            return uriPath.replace("/root".toRegex(), "")
          }
        }
        return getDataColumn(uri)
      }
      return getDataColumn(uri)
    }
  }

  /**
   * BUG : 部分机型进入"文件管理器" 执行到  cursor.getColumnIndexOrThrow(column);出现
   *       Caused by: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: []
   *
   * Fixed :
   *      https://stackoverflow.com/questions/42508383/illegalargumentexception-column-data-does-not-exist
   *
   */
  private fun getDataColumn(
    uri: Uri?,
    selection: String? = null,
    selectionArgs: Array<String>? = null
  ): String? {
    @Suppress("DEPRECATION") val column = MediaStore.Files.FileColumns.DATA
    val projection = arrayOf(column)
    try {
      App.getApplication().contentResolver.query(uri ?: return null, projection, selection, selectionArgs, null)?.use { c: Cursor ->
        if (c.moveToFirst()) {
          val columnIndex = c.getColumnIndex(column)
          return c.getString(columnIndex)
        }
      }
    } catch (e: Throwable) {
      FileLogger.e("getDataColumn -> ${e.message}")
    }
    return null
  }

  //The Uri to check
  //----------------------------------------------------------------

  private fun getGoogleDriveFilePath(uri: Uri, context: Context): String? {
    context.contentResolver.query(uri, null, null, null, null)?.use { c: Cursor ->/*
             Get the column indexes of the data in the Cursor,
             move to the first row in the Cursor, get the data, and display it.
             */
      val nameIndex: Int =
        c.getColumnIndex(OpenableColumns.DISPLAY_NAME) //val sizeIndex: Int = c.getColumnIndex(OpenableColumns.SIZE)
      if (!c.moveToFirst()) {
        return uri.toString()
      }
      val name: String = c.getString(nameIndex) //val size = c.getLong(sizeIndex).toString()
      val file = File(context.cacheDir, name)

      var inputStream: InputStream? = null
      var outputStream: FileOutputStream? = null
      try {
        inputStream = context.contentResolver.openInputStream(uri)
        outputStream = FileOutputStream(file)
        var read = 0
        val maxBufferSize = 1 * 1024 * 1024
        val bytesAvailable: Int = inputStream?.available() ?: 0
        val bufferSize = bytesAvailable.coerceAtMost(maxBufferSize)
        val buffers = ByteArray(bufferSize)
        while (inputStream?.read(buffers)?.also { read = it } != -1) {
          outputStream.write(buffers, 0, read)
        }
      } catch (e: Exception) {
        e.printStackTrace()
      } finally {
        inputStream?.close()
        outputStream?.close()
      }
      return file.path
    }
    return uri.toString()
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is Google Photos.
   */
  fun isGooglePhotosUri(uri: Uri?): Boolean {
    return "com.google.android.apps.photos.content".equals(uri?.authority, true)
  }

  fun isGoogleDriveUri(uri: Uri?): Boolean {
    return "com.google.android.apps.docs.storage.legacy" == uri?.authority || "com.google.android.apps.docs.storage" == uri?.authority
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is local.
   */
  fun isLocalStorageDocument(uri: Uri?): Boolean {
    return AUTHORITY.equals(uri?.authority, true)
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is ExternalStorageProvider.
   */
  private fun isExternalStorageDocument(uri: Uri?): Boolean {
    return "com.android.externalstorage.documents".equals(uri?.authority, true)
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is DownloadsProvider.
   */
  private fun isDownloadsDocument(uri: Uri?): Boolean {
    return "com.android.providers.downloads.documents".equals(uri?.authority, true)
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is MediaProvider.
   */
  private fun isMediaDocument(uri: Uri?): Boolean {
    return "com.android.providers.media.documents".equals(uri?.authority, true)
  }

  /**
   * content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/
   *
   * @param uri
   * @return
   */
  private fun isHuaWeiUri(uri: Uri?): Boolean {
    return "com.huawei.hidisk.fileprovider".equals(uri?.authority, true)
  }

  /**
   * 获取文档元数据(Get document metadata)
   */
  @SuppressLint("Range")
  fun dumpMetaData(uri: Uri?, block: ((displayName: String?, size: String?) -> Unit)? = null) {
    val cursor = App.getApplication().contentResolver.query(uri ?: return, null, null, null, null)

    cursor?.use {
      while (it.moveToNext()) { // moveToFirst die
        val displayName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))

        val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
        val size: String = if (!it.isNull(sizeIndex)) {
          it.getString(sizeIndex)
        } else "Unknown"
        block?.invoke(displayName, size)
        FileLogger.i("Name ：$displayName  Size：$size B")
      }
    }
  }

  /**
   * 依据文件扩展名的类型确定相应的MimeType
   */ //    fun fromSuffix(suffix: String): IFileType {
  //        val end = suffix.lowercase(Locale.getDefault())
  //        values().forEach { t: FileType ->
  //            if (t.mimeArray.isNullOrEmpty()) {
  //                if (t.mimeType?.equals(end, true) == true) return t
  //            } else {
  //                if (t.mimeArray?.contains(end) == true) return t
  //            }
  //        }
  //        return UNKNOWN
  //    }

  /**
   * ContentResolver.query 获取 `文件/文件夹` 大小 (Get the size of `file folder`)
   *
   * @return File Size, Unit Byte
   */
  fun getFileSize(context: Context, uri: Uri?): Long? = uri?.use {
    val zero = 0L
    val uriScheme = uri.scheme
    val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || "content".equals(
        uriScheme,
        true
      )
    ) {
      cursor?.use { c ->
        val sizeIndex: Int =
          c.getColumnIndex(OpenableColumns.SIZE) // 1.Technically the column stores an int, but cursor.getString() will do the conversion automatically.
        // it.getString(sizeIndex)
        // 2.it.moveToFirst() -> Caused by: android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
        if (c.moveToFirst() && !c.isNull(sizeIndex)) c.getLong(sizeIndex) else zero
      }
    } else if ("file".equals(uriScheme, true)) File(
      getPathByUri(uri) ?: return zero
    ).length() else zero
  } // format size

}

/**
 * 扩展函数：uri具备访问权限作用域
 */
inline fun <R> Uri.use(block: Uri.() -> R): R {
  var isAlreadyHavePermission = false
  try {
    isAlreadyHavePermission = giveUriPermission(this)
    return block()
  } catch (t: Throwable) {
    FileLogger.e("giveUriPermission Error ${t.message}")
  } finally {
    if (!isAlreadyHavePermission) {
      try {
        revokeUriPermission(this)
      } catch (t: Throwable) {
        FileLogger.e("revokeUriPermission Error ${t.message}")
      }
    }
  }
  return block()
}

/**
 * @return 传入的Uri是否已具备访问权限 (Whether the incoming Uri has access permission)
 */
fun giveUriPermission(uri: Uri?): Boolean {
  return uri?.run {
    when (App.getApplication().checkUriPermission(
      this,
      android.os.Process.myPid(),
      android.os.Process.myUid(),
      Intent.FLAG_GRANT_READ_URI_PERMISSION
    )) {
      PackageManager.PERMISSION_GRANTED -> true
      PackageManager.PERMISSION_DENIED -> {
        App.getApplication().grantUriPermission(
          App.getApplication().packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION
        )
        false
      }
      else -> false
    }
  } ?: false
}

fun revokeUriPermission(uri: Uri?) {
  App.getApplication().revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}